﻿//Battery Alarm 40-80% V3.0
//Written by Floating Sunfish

using System;
using System.Windows.Forms;

using System.IO;
using IWshRuntimeLibrary;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;

namespace BatteryAlarm40_80Percent
{
    public partial class Form1 : Form
    {
        private int onBattery;
        private int onCharge;
        private bool alertWhenFull;
        private bool shutUp;
        private bool noSounds;
        private bool runOnStartUp;
        private string customAlarmPath;

        private int powerLevel;
        private bool isCharging;

        private string toolTip;
        private ContextMenu m_menu;

        public Form1()
        {
            InitializeComponent();
        }

        //save settings so they persist even after the app is closed
        private void btnSave_Click(object sender, EventArgs e)
        {
            if (txtBattery.Text == "")
            {
                txtBattery.Text = "0";
            }

            if (txtCharge.Text == "")
            {
                txtCharge.Text = "0";
            }

            onBattery = Int32.Parse(txtBattery.Text);
            onCharge = Int32.Parse(txtCharge.Text);
            alertWhenFull = chkAlertWhenFull.Checked;
            shutUp = chkShutUp.Checked;
            noSounds = chkNoSounds.Checked;
            runOnStartUp = chkStartUp.Checked;

            Properties.Settings.Default.OnBattery = onBattery;
            Properties.Settings.Default.OnCharge = onCharge;
            Properties.Settings.Default.AlertWhenFull = alertWhenFull;
            Properties.Settings.Default.ShutUp = shutUp;
            Properties.Settings.Default.NoSounds = noSounds;
            Properties.Settings.Default.RunOnStartUp = runOnStartUp;
            Properties.Settings.Default.CustomAlarmSound = customAlarmPath;

            Properties.Settings.Default.Save();

            //Show the info icon so the user doesn't think the app just vanished
            notifyIcon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info;

            reloadToolTip(); 
            notifyIcon.BalloonTipText = toolTip;

            CreateOrDeleteShortcut();
        }

        private void txtBattery_KeyPress(object sender, KeyPressEventArgs e)
        {
            bool isNumeric = CheckKeyNumeric(e);

            if (isNumeric)
            {
                e.Handled = true;
            }
        }

        private void txtCharge_KeyPress(object sender, KeyPressEventArgs e)
        {
            bool isNumeric = CheckKeyNumeric(e);

            if (isNumeric)
            {
                e.Handled = true;
            }
        }

        //only allow Numeric and Backspace input
        private bool CheckKeyNumeric(KeyPressEventArgs e)
        {
            char ch = e.KeyChar;

            return (!Char.IsDigit(ch) && ch != 8 && ch != 13);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //prevent resizing the window
            this.FormBorderStyle = FormBorderStyle.FixedSingle;

            ReflectSettings();

            //Show the info icon so the user doesn't think the app just vanished          
            notifyIcon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info;
            notifyIcon.BalloonTipTitle = "Battery Alarm 40-80%";
            notifyIcon.Text = "Battery Alarm 40-80%";
            notifyIcon.BalloonTipText = toolTip;

            //right-click menus
            m_menu = new ContextMenu();
            m_menu.MenuItems.Add(0, new MenuItem("Show", new System.EventHandler(Show_Click)));
            m_menu.MenuItems.Add(1, new MenuItem("Do Nothing", new System.EventHandler(ToggleShutUp_Click)));
            m_menu.MenuItems.Add(2, new MenuItem("No Sounds", new System.EventHandler(ToggleNoSounds_Click)));
            m_menu.MenuItems.Add(3, new MenuItem("Exit", new System.EventHandler(Exit_Click)));

            notifyIcon.ContextMenu = m_menu;

            //always start minimized, since we don't want to annoy the user if we're running on StartUp
            this.WindowState = FormWindowState.Minimized;

            //if it's the first-time they're running this, create a shortcut!
            CreateOrDeleteShortcut();
        }

        private void CreateOrDeleteShortcut()
        {
            if (runOnStartUp)
            {
                CreateStartupFolderShortcut();
            }
            else
            {
                DeleteStartupFolderShortcuts(Path.GetFileName(Application.ExecutablePath));
            }
        }

        protected void Exit_Click(Object sender, System.EventArgs e)
        {
            //properly exit
            if (System.Windows.Forms.Application.MessageLoop)
            {
                // WinForms app
                System.Windows.Forms.Application.Exit();
            }
            else
            {
                // Console app
                System.Environment.Exit(1);
            }
        }

        protected void Show_Click(Object sender, System.EventArgs e)
        {
            Show();
            WindowState = FormWindowState.Normal;
            this.ShowInTaskbar = true;
        }

        //we only want to *toggle* the setting, NOT save it.
        protected void ToggleShutUp_Click(Object sender, System.EventArgs e)
        {
            if (shutUp)
            {
                Properties.Settings.Default.ShutUp = false;
                shutUp = false;
            }
            else
            {
                Properties.Settings.Default.ShutUp = true;
                shutUp = true;
            }
            ReflectSettings();
        }

        //we only want to *toggle* the setting, NOT save it.
        protected void ToggleNoSounds_Click(Object sender, System.EventArgs e)
        {
            if (noSounds)
            {
                Properties.Settings.Default.NoSounds = false;
                noSounds = false;
            }
            else
            {
                Properties.Settings.Default.NoSounds = true;
                noSounds = true;
            }
            ReflectSettings();
        }

        //reflects Settings to the UI
        private void ReflectSettings()
        {
            //load system settings
            onBattery = Properties.Settings.Default.OnBattery;
            onCharge = Properties.Settings.Default.OnCharge;
            alertWhenFull = Properties.Settings.Default.AlertWhenFull;
            shutUp = Properties.Settings.Default.ShutUp;
            noSounds = Properties.Settings.Default.NoSounds;
            runOnStartUp = Properties.Settings.Default.RunOnStartUp;

            //and display accordingly
            txtBattery.Text = onBattery.ToString();
            txtCharge.Text = onCharge.ToString();
            chkAlertWhenFull.Checked = alertWhenFull;
            chkShutUp.Checked = shutUp;
            chkNoSounds.Checked = noSounds;
            chkStartUp.Checked = runOnStartUp;
        }

        //minimize app to system tray
        private void Form1_Resize(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                reloadToolTip();
                notifyIcon.Visible = true;
                notifyIcon.ShowBalloonTip(3000);
                this.ShowInTaskbar = false;
            }
        }

        //restore app when user double-clicks on system tray
        private void notifyIcon_DoubleClick(object sender, EventArgs e)
        {
            Show();
            WindowState = FormWindowState.Normal;
            this.ShowInTaskbar = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            PowerStatus p = SystemInformation.PowerStatus;
            powerLevel = (int)(p.BatteryLifePercent * 100);
            isCharging = p.PowerLineStatus == PowerLineStatus.Online;

            //don't bother with notifications if there's no battery on the laptop
            if (p.BatteryChargeStatus.ToString() != "NoSystemBattery")
            {
                //only alert on 100% if charging
                if (alertWhenFull && powerLevel == 100 && isCharging)
                {
                    if (!noSounds)
                    {
                        PlaySound();
                    }
                    
                    notifyIcon.BalloonTipText = "Your battery level is now at 100% \n Please unplug your charger.";
                    notifyIcon.ShowBalloonTip(3000);
                    reloadToolTip();

                    //we don't want to bombard the user with notifications!
                    timer1.Enabled = false;
                }

                if (onBattery > 0 && onCharge > 0)
                {
                    if (isCharging)
                    {
                        if (!shutUp && powerLevel >= onCharge)
                        {
                            if (!noSounds)
                            {
                                PlaySound();
                            }

                            notifyIcon.BalloonTipText = "Your battery level is now at " + onCharge + "% \n Please unplug your charger.";
                            notifyIcon.ShowBalloonTip(3000);
                            reloadToolTip();

                            //we don't want to bombard the user with notifications!
                            timer1.Enabled = false;
                        }
                    }
                    else
                    {
                        if (!shutUp && powerLevel <= onBattery)
                        {
                            if (!noSounds)
                            {
                                PlaySound();
                            }

                            notifyIcon.BalloonTipText = "Your battery level is now at " + onBattery + "% \n Please plug-in your charger.";
                            notifyIcon.ShowBalloonTip(3000);
                            reloadToolTip();

                            //we don't want to bombard the user with notifications!
                            timer1.Enabled = false;
                        }
                    }
                }
            }
        }

        public void PlaySound()
        {
            string path;
            path = Properties.Settings.Default.CustomAlarmSound;
            if (path != "" && System.IO.File.Exists(path))
            {
                //use custom alarm sound
                SoundEffect sfx = new SoundEffect(path);
                sfx.Play();
            }
            else
            {
                //use default alarm sound
                SoundEffect sfx = new SoundEffect(@"c:\Windows\Media\notify.wav");
                sfx.Play();
            }
        }

        //resets tooltip to original message
        public void reloadToolTip()
        {
            if (shutUp || ((onBattery == 0 || onCharge == 0) && !alertWhenFull))
            { 
                toolTip = "Battery Alarm 40-80% will be on standby.";
            }
            else if (onBattery > 0 && onCharge > 0)
            {
                toolTip = "Battery Alarm 40-80% will notify you when your battery level reaches " + onBattery.ToString()
                + "% on battery and " + onCharge.ToString() + "% while charging.";
            }
            else if (alertWhenFull)
            {
                toolTip = "Battery Alarm 40-80% will notify you when your battery level reaches 100%";
            }
            notifyIcon.BalloonTipText = toolTip;
        }

        //every ten seconds, check if we should revive our first timer, since it shuts down after a message is shown
        private void timer2_Tick(object sender, EventArgs e)
        {
            if (!timer1.Enabled)
            {
                timer1.Enabled = true;
            }
        }

        public void CreateStartupFolderShortcut()
        {
            WshShellClass wshShell = new WshShellClass();
            IWshRuntimeLibrary.IWshShortcut shortcut;
            string startUpFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);

            // Create the shortcut
            shortcut = (IWshRuntimeLibrary.IWshShortcut)wshShell.CreateShortcut(startUpFolderPath + "\\" + Application.ProductName + ".lnk");

            shortcut.TargetPath = Application.ExecutablePath;
            shortcut.WorkingDirectory = Application.StartupPath;
            shortcut.Description = "Launch My Application";
            shortcut.IconLocation = Application.StartupPath + @"\BatteryAlarm4080.ico";  
            shortcut.Save();
        }

        public string GetShortcutTargetFile(string shortcutFilename)
        {
            string pathOnly = Path.GetDirectoryName(shortcutFilename);
            string filenameOnly = Path.GetFileName(shortcutFilename);

            Shell32.Shell shell = new Shell32.ShellClass();
            Shell32.Folder folder = shell.NameSpace(pathOnly);
            Shell32.FolderItem folderItem = folder.ParseName(filenameOnly);
            if (folderItem != null)
            {
                Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
                return link.Path;
            }

            return String.Empty; //Not found
        }

        public void DeleteStartupFolderShortcuts(string targetExeName)
        {
            string startUpFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);

            DirectoryInfo di = new DirectoryInfo(startUpFolderPath);
            FileInfo[] files = di.GetFiles("*.lnk");

            foreach (FileInfo fi in files)
            {
                string shortcutTargetFile = GetShortcutTargetFile(fi.FullName);
                Console.WriteLine("{0} -> {1}", fi.Name, shortcutTargetFile);

                if (shortcutTargetFile.EndsWith(targetExeName, StringComparison.InvariantCultureIgnoreCase))
                {
                    System.IO.File.Delete(fi.FullName);
                }
            }
        }

        //links to my Mitsudomoe blog. ;)
        private void sunfishLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            ProcessStartInfo sInfo = new ProcessStartInfo("https://sunfishscans.wordpress.com/");
            Process.Start(sInfo);
        }

        private void btnSound_Click(object sender, EventArgs e)
        {
            DialogResult result = openFileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                string file = openFileDialog.FileName;
                try
                {
                    customAlarmPath = file;
                    MimeCheck wavChecker = new MimeCheck();

                    //check if file exists
                    if (System.IO.File.Exists(customAlarmPath))
                    {
                        //unfortunately, we can only accept WAV files at the moment
                        if (wavChecker.isWave(customAlarmPath))
                        {
                            Properties.Settings.Default.CustomAlarmSound = customAlarmPath;
                            Properties.Settings.Default.Save(); //auto-save
                        }
                        else
                        {
                            MessageBox.Show("Please select a valid .WAV file.",
                            "Invalid Audio File",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Exclamation,
                            MessageBoxDefaultButton.Button1);
                        }
                    }
                    else
                    {
                        //silently use default alarm sound if we can't find the file ;P
                        btnResetSound.PerformClick();
                    }
                }
                catch (IOException)
                {
                    //silently use default alarm sound if we can't find the file ;P
                    btnResetSound.PerformClick();
                }
            }
        }

        private void btnResetSound_Click(object sender, EventArgs e)
        {
            customAlarmPath = "";
            Properties.Settings.Default.CustomAlarmSound = customAlarmPath;
            Properties.Settings.Default.Save(); //auto-save
        }
    }

    //special thanks to codeproject.com's Ron Beyer for the solution
    public class SoundEffect
    {
        string _soundFile;
        Thread _soundThread;
        bool _isStopped = true;

        public bool IsFinished { get { return _isStopped; } }

        public SoundEffect(string soundFile)
        {
            _soundFile = soundFile;
        }

        public void Play()
        {
            if (!_isStopped)
                return;

            _soundThread = new Thread(PlayThread);
            _soundThread.Start();
        }

        private void PlayThread()
        {
            _isStopped = false;
            System.Media.SoundPlayer player = new System.Media.SoundPlayer(_soundFile);
            player.PlaySync();
            _isStopped = true;
        }
    }

    //special thanks to daniweb.com's Diamonddrake for the solution
    class MimeCheck
    {
        [DllImport(@"urlmon.dll", CharSet = CharSet.Auto)]
        private extern static System.UInt32 FindMimeFromData(
            System.UInt32 pBC,
            [MarshalAs(UnmanagedType.LPStr)] System.String pwzUrl,
            [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer,
            System.UInt32 cbSize,
            [MarshalAs(UnmanagedType.LPStr)] System.String pwzMimeProposed,
            System.UInt32 dwMimeFlags,
            out System.UInt32 ppwzMimeOut,
            System.UInt32 dwReserverd
        );
        public string getMimeFromFile(string filename)
        {
            if (!System.IO.File.Exists(filename))
                throw new FileNotFoundException(filename + " not found");
            byte[] buffer = new byte[256];
            using (FileStream fs = new FileStream(filename, FileMode.Open))
            {
                if (fs.Length >= 256)
                    fs.Read(buffer, 0, 256);
                else
                    fs.Read(buffer, 0, (int)fs.Length);
            }
            try
            {
                System.UInt32 mimetype;
                FindMimeFromData(0, null, buffer, 256, null, 0, out mimetype, 0);
                System.IntPtr mimeTypePtr = new IntPtr(mimetype);
                string mime = Marshal.PtrToStringUni(mimeTypePtr);
                Marshal.FreeCoTaskMem(mimeTypePtr);
                return mime;
            }
            catch (Exception e)
            {
                return "unknown/unknown";
            }
        }
        public bool isWave(string FileName)
        {
            if ("audio/wav" == getMimeFromFile(FileName))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}
